Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enumoji implementation in C# 14.0 #2

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

jbtule
Copy link

@jbtule jbtule commented Sep 10, 2020

  • Lighter weight syntax reduces LOC by half with identical calls. (direct port)
  • Enumojis
  • Switched to blog emojis (compound 🧑‍🚀 emoji in master wasn't overwriting properly on Mac OS X terminal)

GameOfLife/Program.fs Outdated Show resolved Hide resolved
Copy link
Author

@jbtule jbtule left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example wasn't ported functionally, just syntactically from C#. It about half the lines of code of the original C#. I've annotated this PR to clarify the differences in F#. For further information how how F# is actually a great general purpose programing language, and that it expresses functional, imperative and object oriented code more tersely that C# check out this slideshow: C# Light

let timeout = 500
let mutable runSimulation = true

type Status = ``💀`` = 0 | ``😁`` = 1
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Enumoji's we don't have to use conditional expressions for the 3 data types needed for two states Alive|Dead in this port. We can then use the F# operators string, int, and enum to convert to what we need.

Comment on lines +7 to +9
let rows = 15
let columns = 15
let timeout = 500
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can infer types on our statically scoped variables!

let rows = 15
let columns = 15
let timeout = 500
let mutable runSimulation = true
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

F# requires you to qualify any variable you want to mutate. You save so much boiler plate with type inference, adding this extra 6 let word hardly seems like a thing, and makes it easier to reason about variables. Notice we only have to use the keyword 3 times in this program ported directly from C#.


type Status = ``💀`` = 0 | ``😁`` = 1

let private nextGeneration (currentGrid: Status [,]) =
Copy link
Author

@jbtule jbtule Sep 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although there is a recursive modifier (rec) that can be applied to a module or namespace, typically you declare your functions before you use them in F#. So nextGeneration helper function was moved to the top of the file in this port.

@@ -0,0 +1,67 @@
module GameOfLife.Program
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although you can do C# style static classes in F# with some boilerplate. A module is the preferred vehicle for static functions. CLI wise it will look like a static class in C#.

| Status.``💀`` when aliveNeighbors = 3 -> Status.``😁``
// stays the same
| unchanged -> unchanged
nextGeneration
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In F# the last line in an expression is the returned value. No return keyword necessary, but that also means no easy short circuiting.

let sb = StringBuilder()
for row in 0..(rows-1) do
for column in 0..(columns-1) do
future.[row, column] |> string |> sb.Append |> ignore
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pipe operator |> makes it easy to describe data flowing from one function to another. Otherwise it would have been written with nested parentheses:

ignore(sb.Append(string(future.[row, column])))

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string operator calls ToString() on an object. And the ignore operator is how you discard return values that you are not going to use (Like Append returns a reference to the string builder it's mutating. You'll get a warning if you have an expression that returns a value that isn't unit and it's not the last line of it's enclosing expression.

[<EntryPoint>]
let main _ =
// randomly initialize our grid
let mutable grid = Array2D.init rows columns (fun _ _ -> RandomNumberGenerator.GetInt32(0, 2) |> enum)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

F# array creation functions let you pass a lambda function to be used in initialization. Lambda's are prefixed with fun in F#, it's fun! Since we are randomly filling out the array, I discard the row and column arguments in the lambda. We can use the enum operator which is actually able to infer the enum type statically in this case!!

Comment on lines +56 to +59
Console.CancelKeyPress.Add(
fun _ ->
runSimulation <- false
Console.WriteLine("\n👋 Ending simulation."))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I read somewhere that C# designers wished they never special cased += and -= operators to be AddHandlerand RemoveHandler methods. So those methods are what you can use in F# instead of the operators. To make things simpler without explicitly creating delegates, you can use the Add method with a lambda, as long as you don't need to remove the handler later.

while runSimulation do
print grid
grid <- nextGeneration(grid)
0
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main function is expected to return an integer. So last line is a zero.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant